home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / smail-3.1.28 / src / expand.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-09-20  |  30.9 KB  |  1,368 lines

  1. /* @(#)src/expand.c    1.10 9/20/92 12:54:48 */
  2.  
  3. /*
  4.  *    Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
  5.  *    Copyright (C) 1992  Ronald S. Karr
  6.  * 
  7.  * See the file COPYING, distributed with smail, for restriction
  8.  * and warranty information.
  9.  */
  10.  
  11. /*
  12.  * expand.c:
  13.  *    expand filenames used by directors.
  14.  *
  15.  *    external functions:  expand_string, build_cmd_line
  16.  */
  17. #include <stdio.h>
  18. #include <ctype.h>
  19. #include <pwd.h>
  20. #include "defs.h"
  21. #include "smail.h"
  22. #include "addr.h"
  23. #include "direct.h"
  24. #include "route.h"
  25. #include "transport.h"
  26. #include "lookup.h"
  27. #include "log.h"
  28. #include "alloc.h"
  29. #include "dys.h"
  30. #include "exitcodes.h"
  31. #ifndef DEPEND
  32. # include "debug.h"
  33. # include "extern.h"
  34. #endif
  35.  
  36. /* library functions */
  37. extern long time();
  38.  
  39. /* functions local to this file */
  40. static int expand_string_to();
  41. static int if_clause();
  42. static int lookup_clause();
  43. static int test_cond();
  44. static char **build_argv();
  45. static char *substitute();
  46. static char *strip_fold();
  47. static char *skip_ident();
  48. static int skip_nesting();
  49. static int clause_token();
  50. #ifndef NODEBUG
  51. static void bad_subst();
  52. #endif
  53.  
  54.  
  55. /*
  56.  * expand_string - expand a string containing parts to be expanded
  57.  *
  58.  * This function does ~user and ~/ expansion and also performs expansions
  59.  * of the form $name or ${name}.  See substitute() for the possible
  60.  * substitutions.
  61.  *
  62.  * If `addr' is NULL, then a dummy addr structure is formed with all
  63.  * items NULL except for home and user, which are taken from the
  64.  * arguments to expand_string().
  65.  *
  66.  * return NULL on error attempting expansion.  The area returned may
  67.  * be reused on subsequent calls.  If the caller wishes to retain the
  68.  * returned data, it should be copied elsewhere.
  69.  */
  70. char *
  71. expand_string(string, addr, home, user)
  72.     register char *string;        /* unexpanded string */
  73.     struct addr *addr;            /* addr structure with values */
  74.     char *home;                /* home directory */
  75.     char *user;                /* user name for $user */
  76. {
  77.     static struct str str;        /* build strings here */
  78.     static int inited = FALSE;        /* TRUE if str inited */
  79.     static struct addr *dummy_addr = NULL; /* dummy addr for home and user */
  80.     char *save_string = string;        /* save pointer to start of string */
  81.  
  82.     DEBUG3(DBG_DRIVER_HI, "expand_string(%s, %s, %s) called\n",
  83.        string, home, user);
  84.  
  85.     if (! inited) {
  86.     STR_INIT(&str);
  87.     inited = TRUE;
  88.     } else {
  89.     STR_CHECK(&str);
  90.     str.i = 0;
  91.     }
  92.  
  93.     if (addr == NULL) {
  94.     /* no addr structure given, setup a dummy one */
  95.     if (dummy_addr == NULL) {
  96.         dummy_addr = alloc_addr();
  97.     }
  98.     addr = dummy_addr;
  99.     addr->home = home;
  100.     addr->next_addr = user;
  101.     }
  102.  
  103.     /*
  104.      * do the grunt work of expansion, appending to str
  105.      */
  106.     if (expand_string_to(&str, string, addr, (char *)NULL) == FAIL) {
  107.     return NULL;
  108.     }
  109.  
  110.     /*
  111.      * expansion was done, finish up the string and return it
  112.      */
  113.     STR_NEXT(&str, '\0');
  114.     DEBUG1(DBG_DRIVER_HI, "expand_string returns %s\n", str.p);
  115.     return str.p;
  116. }
  117.  
  118. static int
  119. expand_string_to(str, string, addr, found)
  120.     struct str *str;            /* output string */
  121.     char *string;            /* unexpanded string */
  122.     struct addr *addr;            /* addr structure with values */
  123.     char *found;            /* value for $value */
  124. {
  125.     if (string[0] == '~') {
  126.     /* do some kind of twiddle expansion */
  127.     if (string[1] == '/') {
  128.         /* ~/ turns into home/ */
  129.         if (addr->home) {
  130.         string++;
  131.         STR_CAT(str, addr->home);
  132.         } else {
  133.         /* no home directory, so ~/ is not valid */
  134.         DEBUG(DBG_DRIVER_MID, "no home directory, ~/ invalid\n");
  135.         return FAIL;
  136.         }
  137.     } else {
  138.         /* ~user turns into home director for the given user */
  139.         char *p = string + 1;
  140.         struct passwd *pw;
  141.         extern struct passwd *getpwbyname();
  142.  
  143.         while (*string && *string != '/') {
  144.         string++;
  145.         }
  146.         if (*string) {
  147.         *string = '\0';
  148.         pw = getpwbyname(p);
  149.         *string = '/';
  150.         } else {
  151.         pw = getpwbyname(p);
  152.         }
  153.         if (pw == NULL) {
  154.         /* ~user but username isn't valid */
  155.         DEBUG1(DBG_DRIVER_MID, "user not found, ~%s invalid\n", p);
  156.         return FAIL;
  157.         }
  158.         STR_CAT(str, pw->pw_dir);
  159.     }
  160.     }
  161.  
  162.     /*
  163.      * we have the silly ~ business out of the way, now
  164.      * get all of the rest of the silly business out of the way
  165.      */
  166.     while (*string) {
  167.     if (*string == '$') {
  168.         /* do a $-substitution */
  169.         string++;
  170.         if (*string == '{') {
  171.         /*
  172.          * handle expansions of the form ${name}
  173.          */
  174.         char *p, *q, *new;
  175.  
  176.         p = ++string;
  177.         if (skip_nesting(&string) == FAIL)
  178.             return FAIL;
  179.         --string;
  180.         /*
  181.          * process conditional expansion
  182.          */
  183.         q = skip_ident(p, string);
  184.         if ((q - p) == 2 && strncmpic(p, "if", 2) == 0) {
  185.             if (if_clause(addr, found, str, q, string) == FAIL) {
  186. #ifndef NODEBUG
  187.             bad_subst(p, string - p);
  188. #endif
  189.             return FAIL;
  190.             }
  191.         }
  192.         /*
  193.          * process database lookup
  194.          */
  195.         else if ((q - p) == 6 && strncmpic(p, "lookup", 6) == 0) {
  196.             if (lookup_clause(addr, str, q, string) == FAIL) {
  197. #ifndef NODEBUG
  198.             bad_subst(p, string - p);
  199. #endif
  200.             return FAIL;
  201.             }
  202.         } else {
  203.             /* not conditional, just a variable */
  204.             new = substitute(addr, (char *)NULL, found, p, string - p);
  205.             if (new) {
  206.             STR_CAT(str, new);
  207.             } else {
  208.             /* unrecognized substitution */
  209. #ifndef NODEBUG
  210.             bad_subst(p, string - p);
  211. #endif
  212.             return FAIL;
  213.             }
  214.         }
  215.         string++;
  216.         } else {
  217.         /*
  218.          * handle $name expansions
  219.          */
  220.         char *p = string;
  221.         char *new;
  222.  
  223.         string = skip_ident(string + 1, NULL);
  224.         new = substitute(addr, (char *)NULL, found, p, string - p);
  225.         if (new) {
  226.             STR_CAT(str, new);
  227.         } else {
  228.             /* unrecognized substitution */
  229. #ifndef NODEBUG
  230.             bad_subst(p, string - p);
  231. #endif
  232.             return FAIL;
  233.         }
  234.         }
  235.     } else {
  236.         /*
  237.          * regular character, copy it into the result
  238.          */
  239.         STR_NEXT(str, *string++);
  240.     }
  241.     }
  242.     return SUCCEED;
  243. }
  244.  
  245. /*
  246.  * if_clause - plause ${if ...} expansion
  247.  */
  248.  
  249. static int
  250. if_clause(addr, found, str, s, es)
  251.     struct addr *addr;
  252.     char *found;
  253.     struct str *str;
  254.     char *s;
  255.     char *es;
  256. {
  257.     int result;
  258.     char *then_clause, *then_end, *else_clause, *else_end;
  259.     char *q = s;
  260.  
  261.     while (isspace(*q))
  262.     q++;
  263.     result = test_cond(addr, found, &q, es);
  264.     if (result == FAIL || q >= es) {
  265.     return FAIL;
  266.     }
  267.     while (isspace(*q))
  268.     q++;
  269.  
  270.     /*
  271.      * find then and else clauses of if.  Two forms
  272.      * are possible:
  273.      *    ${if : condition [:]then-clause}
  274.      *  ${if : condition {then-clause} [else] [{else-clause}]}
  275.      */
  276.  
  277.     switch (*q) {
  278.     case '{':
  279.     then_clause = ++q;
  280.     if (skip_nesting(&q) == FAIL)
  281.         return FAIL;
  282.     then_end = q - 1;
  283.     while (isspace(*q))
  284.         q++;
  285.     if (*q != '{' && strncmpic(q, "else", 4) == 0) {
  286.         q += 4;
  287.         while (isspace(*q))
  288.         q++;
  289.     }
  290.     if (*q == '}') {
  291.         else_clause = es;
  292.         else_end = es;
  293.         break;
  294.     }
  295.     if (*q != '{') {
  296.         return FAIL;
  297.     }
  298.     else_clause = ++q;
  299.     if (skip_nesting(&q) == FAIL)
  300.         return FAIL;
  301.     else_end = q - 1;
  302.     while (isspace(*q))
  303.         q++;
  304.     if (q != es) {
  305.         return FAIL;
  306.     }
  307.     break;
  308.  
  309.     case ':':
  310.     q++;
  311.     /* FALL THROUGH */
  312.     default:
  313.     then_clause = q;
  314.     then_end = es;
  315.     else_clause = es;
  316.     else_end = es;
  317.     break;
  318.     }
  319.  
  320.     /*
  321.      * expand the then clause of the result matches
  322.      * the indicated test sense if: is a true sense,
  323.      * if! is a false sense; otherwise, expand the
  324.      * else clause.
  325.      */
  326.  
  327.     if (result)
  328.     s = rcopy(then_clause, then_end);
  329.     else
  330.     s = rcopy(else_clause, else_end);
  331.     if (expand_string_to(str, s, addr, found) == FAIL) {
  332.     xfree(s);
  333.     return FAIL;
  334.     }
  335.     xfree(s);
  336.     return SUCCEED;
  337. }
  338.  
  339. /*
  340.  * test_cond - test a conditional within an ${if: ...} or ${if! ...}
  341.  *
  342.  * Possible conditionals:
  343.  *
  344.  *    !condition    - reverse sense of test
  345.  *    def:variable    - true if variable is defined and non-empty
  346.  *    header:string    - true if indicated header exists in message
  347.  *    origin:local    - true if message origin is local
  348.  *    origin:remote    - true if message origin is remote
  349.  *    dest:local    - true if message is being delivered locally
  350.  *    dest:remote    - true if message is being delivered to remote host
  351.  *    xform:local    - transform into a local header
  352.  *    xform:inet    - transform into a RFC822-conformant envelope
  353.  *    xform:uucp    - transform into a UUCP-zone envelope
  354.  *    xform:none    - no specified transformation
  355.  *    eq{var}{value}    - test for match of variable with a value
  356.  *    or{{cond}...}    - logical or of conditionals
  357.  *    and{{cond}...}    - logical and of conditionals
  358.  */
  359.  
  360. static int
  361. test_cond(addr, found, sp, es)
  362.     struct addr *addr;
  363.     char *found;
  364.     char **sp;
  365.     char *es;
  366. {
  367. #define RESULT(res)    ((negate)? ((res)?FALSE:TRUE): ((res)?TRUE:FALSE))
  368.  
  369.     char *q = *sp;
  370.     char *s, *s2;
  371.     char *new;
  372.     int len, len2;
  373.     struct list *hdr;
  374.     long tpflags;
  375.     struct str str;
  376.     int result;
  377.     int negate = FALSE;
  378.  
  379.     while (isspace(*q))
  380.     q++;
  381.     if (*q == '!') {
  382.     negate = TRUE;
  383.     q++;
  384.     }
  385.     *sp = q;
  386.     if (clause_token(sp, &s, &len) == FAIL)
  387.     return FAIL;
  388.     if (len == 3 && strncmpic(s, "def", 3) == 0) {
  389.     if (clause_token(sp, &s, &len) == FAIL)
  390.         return FAIL;
  391.     new = substitute(addr, (char *)NULL, found, s, len);
  392.     return (new && *new)? RESULT(TRUE): RESULT(FALSE);
  393.     }
  394.  
  395.     if (len == 6 && strncmpic(s, "header", 6) == 0) {
  396.     if (clause_token(sp, &s, &len) == FAIL)
  397.         return FAIL;
  398.     for (hdr = header; hdr; hdr = hdr->succ) {
  399.         if (strncmpic(s, hdr->text, len) == 0 &&
  400.         (hdr->text[len] == ':' || isspace(hdr->text[len])))
  401.         {
  402.         return RESULT(TRUE);
  403.         }
  404.     }
  405.     return RESULT(FALSE);
  406.     }
  407.  
  408.     if (len == 6 && strncmpic(s, "origin", 6) == 0) {
  409.     if (clause_token(sp, &s, &len) == FAIL)
  410.         return FAIL;
  411.     if (len == 5 && strncmpic(s, "local", 5) == 0)
  412.         return RESULT(islocal);
  413.     if (len == 6 && strncmpic(s, "remote", 6) == 0)
  414.         return RESULT(! islocal);
  415.     return FAIL;
  416.     }
  417.  
  418.     if (len == 4 && strncmpic(s, "dest", 4) == 0) {
  419.     if (clause_token(sp, &s, &len) == FAIL)
  420.         return FAIL;
  421.     if (addr == NULL || addr->transport == NULL)
  422.         return RESULT(FALSE);
  423.     tpflags = addr->transport->flags;
  424.     if (len == 5 && strncmpic(s, "local", 5) == 0)
  425.         return RESULT(tpflags & LOCAL_TPORT);
  426.     if (len == 6 && strncmpic(s, "remote", 6) == 0)
  427.         return RESULT(! (tpflags & LOCAL_TPORT));
  428.     return FAIL;
  429.     }
  430.  
  431.     if (len == 5 && strncmpic(s, "xform", 5) == 0) {
  432.     if (clause_token(sp, &s, &len) == FAIL)
  433.         return FAIL;
  434.     if (addr == NULL || addr->transport == NULL)
  435.         return FALSE;
  436.     tpflags = addr->transport->flags;
  437.     if (len == 5 && strncmpic(s, "local", 5) == 0)
  438.         return RESULT(tpflags & LOCAL_XFORM);
  439.     if (len == 6 && strncmpic(s, "uucp", 6) == 0)
  440.         return RESULT(tpflags & UUCP_XFORM);
  441.     if (len == 6 && strncmpic(s, "inet", 6) == 0)
  442.         return RESULT(tpflags & INET_XFORM);
  443.     if (len == 6 && strncmpic(s, "none", 6) == 0)
  444.         return RESULT(! (tpflags & (LOCAL_XFORM|UUCP_XFORM|INET_XFORM)));
  445.     return FAIL;
  446.     }
  447.  
  448.     if (len == 2 && strncmpic(s, "eq", 2) == 0) {
  449.     if (clause_token(sp, &s, &len) == FAIL)
  450.         return FAIL;
  451.     if (clause_token(sp, &s2, &len2) == FAIL)
  452.         return FAIL;
  453.     s2 = rcopy(s2, s2 + len2);
  454.     STR_INIT(&str);
  455.     result = RESULT(FALSE);
  456.     if (expand_string_to(&str, s2, addr, found) == FAIL) {
  457.         result = FAIL;
  458.     } else {
  459.         s = substitute(addr, (char *)NULL, found, s, len);
  460.         STR_NEXT(&str, '\0');
  461.         if (strcmp(s, str.p) == 0)
  462.         result = RESULT(TRUE);
  463.     }
  464.     STR_FREE(&str);
  465.     xfree(s2);
  466.     return result;
  467.     }
  468.  
  469.     if (len == 3 && strncmpic(s, "and", 3) == 0) {
  470.     if (clause_token(sp, &s, &len) == FAIL)
  471.         return FAIL;
  472.     s = rcopy(s, s + len);
  473.     q = s;
  474.     result = TRUE;
  475.     for (;;) {
  476.         while (isspace(*q))
  477.         q++;
  478.         if (*q == '\0')
  479.         break;
  480.         if (clause_token(&q, &s2, &len2) == FAIL) {
  481.         result = FAIL;
  482.         break;
  483.         }
  484.         result = test_cond(addr, found, &s2, s2 + len2);
  485.         if (result != TRUE)
  486.         break;
  487.     }
  488.     xfree(s);
  489.     return result == FAIL? FAIL: RESULT(result);
  490.     }
  491.  
  492.     if (len == 2 && strncmpic(s, "or", 2) == 0) {
  493.     if (clause_token(sp, &s, &len) == FAIL)
  494.         return FAIL;
  495.     s = rcopy(s, s + len);
  496.     q = s;
  497.     result = FALSE;
  498.     for (;;) {
  499.         while (isspace(*q))
  500.         q++;
  501.         if (*q == '\0')
  502.         break;
  503.         if (clause_token(&q, &s2, &len2) == FAIL) {
  504.         result = FAIL;
  505.         break;
  506.         }
  507.         result = test_cond(addr, found, &s2, s2 + len2);
  508.         if (result != FALSE)
  509.         break;
  510.     }
  511.     xfree(s);
  512.     return result == FAIL? FAIL: RESULT(result);
  513.     }
  514.  
  515.     return FAIL;
  516. #undef RESULT
  517. }
  518.  
  519. /*
  520.  * lookup_clause - expand a lookup expansion
  521.  *
  522.  * Form:
  523.  *    ${lookup:key:proto{file-expansion}
  524.  *        [then] {then-clause}
  525.  *        [else] {else-clause}}
  526.  * or:
  527.  *    ${lookup:key:proto:file-expansion:then-clause}
  528.  */
  529.  
  530. static int
  531. lookup_clause(addr, str, s, es)
  532.     struct addr *addr;
  533.     struct str *str;
  534.     char *s;
  535.     char *es;
  536. {
  537.     char *q = s;
  538.     char *key, *xkey, *proto, *file;
  539.     int key_len, proto_len, file_len;
  540.     char *then_clause, *then_end, *else_clause, *else_end;
  541.     struct str file_str;
  542.     char *dbinfo;
  543.     char *error;
  544.     char *found = NULL;
  545.     int success;
  546.  
  547.     /*
  548.      * get the key, proto, and database
  549.      */
  550.  
  551.     if (clause_token(&q, &key, &key_len) == FAIL ||
  552.     clause_token(&q, &proto, &proto_len) == FAIL ||
  553.     clause_token(&q, &file, &file_len) == FAIL)
  554.     {
  555.     return FAIL;
  556.     }
  557.  
  558.     /*
  559.      * locate then and else clauses
  560.      */
  561.  
  562.     while (isspace(*q))
  563.     q++;
  564.     if (strncmpic(q, "then", 4) == 0) {
  565.     q += 4;
  566.     while (isspace(*q))
  567.         q++;
  568.     }
  569.     switch (*q) {
  570.     case '{':
  571.     then_clause = ++q;
  572.     if (skip_nesting(&q) == FAIL)
  573.         return FAIL;
  574.     then_end = q - 1;
  575.     while (isspace(*q))
  576.         q++;
  577.     if (*q != '{' && strncmpic(q, "else", 4) == 0) {
  578.         q += 4;
  579.         while (isspace(*q))
  580.         q++;
  581.     }
  582.     if (*q == '}') {
  583.         else_clause = es;
  584.         else_end = es;
  585.         break;
  586.     }
  587.     if (*q != '{')
  588.         return FAIL;
  589.     else_clause = ++q;
  590.     if (skip_nesting(&q) == FAIL)
  591.         return FAIL;
  592.     else_end = q - 1;
  593.     while (isspace(*q))
  594.         q++;
  595.     if (q != es)
  596.         return FAIL;
  597.     break;
  598.  
  599.     case ':':
  600.     q++;
  601.     /* FALL THROUGH */
  602.  
  603.     default:
  604.     then_clause = q;
  605.     then_end = es;
  606.     else_clause = es;
  607.     else_end = es;
  608.     break;
  609.     }
  610.  
  611.     /*
  612.      * expand the key, file, and proto
  613.      */
  614.  
  615.     xkey = substitute(addr, (char *)NULL, (char *)NULL, key, key_len);
  616.     if (xkey == NULL)
  617.     goto do_else_clause;
  618.     xkey = COPY_STRING(xkey);
  619.     STR_INIT(&file_str);
  620.     file = rcopy(file, file + file_len);
  621.     if (expand_string_to(&file_str, file, addr, (char *)NULL) == FAIL) {
  622.     xfree(xkey);
  623.     xfree(file);
  624.     STR_FREE(&file_str);
  625.     return FAIL;
  626.     }
  627.     STR_NEXT(&file_str, '\0');
  628.     proto = rcopy(proto, proto + proto_len);
  629.  
  630.     /*
  631.      * open the database and do a lookup with the key
  632.      */
  633.  
  634.     success = open_database(file_str.p, proto, 1, 2, (struct statbuf *)NULL,
  635.                 &dbinfo, &error);
  636.     switch (success) {
  637.     case FILE_FAIL:
  638.     DEBUG3(DBG_DRIVER_LO, "Warning: open of %s:%s failed: %s\n",
  639.            proto, file_str.p, error);
  640.     goto do_else_clause;
  641.  
  642.     case FILE_AGAIN:
  643.     case FILE_NOMATCH:
  644.     goto do_else_clause;
  645.     }
  646.  
  647.     success = lookup_database(dbinfo, xkey, &found, &error);
  648.     close_database(dbinfo);
  649.  
  650.     switch (success) {
  651.     case FILE_FAIL:
  652.     case DB_FAIL:
  653.     DEBUG4(DBG_DRIVER_LO, "Warning: lookup of %s in %s:%s failed: %s\n",
  654.            xkey, proto, file_str.p, error);
  655.     goto do_else_clause;
  656.  
  657.     case FILE_AGAIN:
  658.     case FILE_NOMATCH:
  659.     case DB_AGAIN:
  660.     case DB_NOMATCH:
  661.     goto do_else_clause;
  662.     }
  663.  
  664.     /*
  665.      * remove white-space from beginning and end of found string
  666.      */
  667.     while (isspace(*found))
  668.     found++;
  669.     for (q = found; *q; q++) {
  670.     if (*q == '#') {
  671.         break;
  672.     }
  673.     }
  674.     while (q > found && isspace(*(q - 1)))
  675.     --q;
  676.     *q = '\0';
  677.  
  678.     /*
  679.      * expand the then-clause
  680.      */
  681.  
  682.     s = rcopy(then_clause, then_end);
  683.     goto do_expand;
  684.  
  685. do_else_clause:
  686.     s = rcopy(else_clause, else_end);
  687.     /* FALL THROUGH */
  688.  
  689. do_expand:
  690.     success = expand_string_to(str, s, addr, found);
  691.     xfree(xkey);
  692.     xfree(proto);
  693.     xfree(file);
  694.     xfree(s);
  695.     STR_FREE(&file_str);
  696.     return success;
  697. }
  698.  
  699.  
  700. /*
  701.  * build_cmd_line - build up an arg vector suitable for execv
  702.  *
  703.  * transports can call this to build up a command line in a standard
  704.  * way.  Of course, if they want to they can build up a command line in
  705.  * a totally different fashion.
  706.  *
  707.  * Caution: return value points to a region which may be reused by
  708.  *        subsequent calls to build_cmd_line()
  709.  *
  710.  * Notes on the replacement algorithm:
  711.  *   o    Within a $( and $) pair, substitutions are made once for
  712.  *    each address on the input list.
  713.  *   o    Otherwise the substitution is made relative to the first
  714.  *    address on the input list.
  715.  *   o    Substitutions:
  716.  *    o  grade ==> $grade
  717.  *    o  addr->next_host ==> $host
  718.  *    o  addr->next_addr ==> $addr or $user
  719.  *    o  addr->home ==> $home or $HOME
  720.  *    o  sender ==> $from or $sender
  721.  *    o  file ==> $file
  722.  *    o  message_id ==> $message_id
  723.  *    o  unix_date() ==> $ctime
  724.  *    o  get_arpa_date() ==> $date
  725.  *    o  getpid() ==> $$
  726.  *    o  uucp_name ==> $uucp_name
  727.  *    o  visible_name ==> $visible_name
  728.  *    o  primary_name ==> $primary_name
  729.  *    o  VERSION ==> $version
  730.  *    o  version() ==> $version_string
  731.  *   o    single quotes, double quotes and backslash work as with /bin/sh
  732.  *
  733.  * return NULL for parsing errors, and load `error' with a message
  734.  * explaining the error.
  735.  */
  736. char **
  737. build_cmd_line(cmd, addr, file, error)
  738.     register char *cmd;            /* input command line */
  739.     struct addr *addr;            /* list of remote addresses */
  740.     char *file;                /* substitution for $file */
  741.     char **error;            /* error message */
  742. {
  743.     static struct str str;        /* generated region */
  744.     static int inited = FALSE;        /* TRUE if str has been inited */
  745.     char *mark;                /* temp mark in cmd line */
  746.     char *new;                /* new string from substitute */
  747.     int ct = 1;                /* count of args, at least one */
  748.     int state = 0;            /* notes about parse state */
  749.     struct addr *save_addr = addr;    /* replace addr from this after $) */
  750.     char *save_cmd;            /* start of a $( ... $) group */
  751.     int last_char = '\0';        /* hold last *cmd value */
  752. #define DQUOTE    0x01            /* double quote in effect */
  753. #define GROUP    0x02            /* $( ... $) grouping in effect */
  754.  
  755.     /* initialize for building up the arg vectors */
  756.     if (! inited) {
  757.     STR_INIT(&str);
  758.     inited = TRUE;
  759.     } else {
  760.     STR_CHECK(&str);
  761.     str.i = 0;
  762.     }
  763.  
  764.     while (*cmd) {
  765.     switch (*cmd) {
  766.     case '\'':
  767.         /* after "'" copy literally to before next "'" char */
  768.         mark = index(cmd+1, '\'');
  769.         if (mark == NULL) {
  770.         panic(EX_DATAERR, "no matching ' for cmd in transport %s",
  771.               addr->transport->name);
  772.         /*NOTREACHED*/
  773.         }
  774.         *mark = '\0';        /* put null in for copy */
  775.         STR_CAT(&str, cmd+1);
  776.         *mark = '\'';        /* put quote back */
  777.         last_char = '\'';
  778.         cmd = mark;
  779.         break;
  780.  
  781.     case '\\':
  782.         /*
  783.          * char after \ is literal, unless in quote, in which case
  784.          * this is not so if the following char is not " or $ or \
  785.          */
  786.         if (*cmd++ == '\0') {
  787.         *error = "\\ at end of command";
  788.         return NULL;
  789.         }
  790.         if (!(state&DQUOTE) ||
  791.         *cmd == '\\' || *cmd == '"' || *cmd == '$')
  792.         {
  793.         STR_NEXT(&str, *cmd);
  794.         } else {
  795.         STR_NEXT(&str, '\\');
  796.         STR_NEXT(&str, *cmd);
  797.         }
  798.         last_char = '\\';
  799.         break;
  800.  
  801.     case '"':            /* double quote is a toggle */
  802.         state ^= DQUOTE;
  803.         last_char = '"';
  804.         break;
  805.  
  806.     case '$':            /* perform parameter substitution */
  807.         cmd++;
  808.         if (*cmd == '\0') {
  809.         *error = "$ at end of command";
  810.         return NULL;
  811.         }
  812.         if (*cmd == '(') {
  813.         if (state&GROUP) {
  814.             *error = "recursive $( ... $)";
  815.             return NULL;
  816.         }
  817.         if (state&DQUOTE) {
  818.             *error = "$( illegal inside \"...\"";
  819.             return NULL;
  820.         }
  821.         save_cmd = cmd;
  822.         state |= GROUP;
  823.         break;
  824.         }
  825.         if (*cmd == ')') {
  826.         if ((state&GROUP) == 0) {
  827.             *error = "no match for $)";
  828.             return NULL;
  829.         }
  830.         if (state&DQUOTE) {
  831.             *error = "$) illegal inside \"...\"";
  832.             return NULL;
  833.         }
  834.         if (!isspace(last_char)) {
  835.             /* end previous vector, create a new one */
  836.             ct++;
  837.             STR_NEXT(&str, '\0');
  838.         }
  839.         addr = addr->succ;
  840.         if (addr) {
  841.             cmd = save_cmd;
  842.         } else {
  843.             /* no more addrs to put in group */
  844.             addr = save_addr;
  845.             state &= ~GROUP;
  846.         }
  847.         last_char = ' ';    /* don't create an extra vector */
  848.         break;
  849.         }
  850.         if (*cmd == '{') {
  851.         mark = cmd+1;
  852.         cmd = index(mark, '}');
  853.         if (cmd == NULL) {
  854.             *error =  "no match for {";
  855.             return NULL;
  856.         }
  857.         } else {
  858.         /* use at least one char after $ for substitute name */
  859.         mark = cmd;
  860.         cmd = skip_ident(cmd, cmd + strlen(cmd));
  861.         /* cmd now one beyond where it should be */
  862.         }
  863.         new = substitute(addr, file, (char *)NULL, mark, cmd - mark);
  864.         if (new == NULL) {
  865.         int c_save = mark[cmd-mark];
  866.  
  867.         mark[cmd-mark] = '\0';
  868.         /* TODO: This is a memory leak */
  869.         *error = xprintf("bad substition: $%s", mark);
  870.         mark[cmd-mark] = c_save;
  871.         return NULL;
  872.         }
  873.         STR_CAT(&str, new);
  874.         if (*cmd != '}') {
  875.         --cmd;            /* correct next char pointer */
  876.         }
  877.         last_char = '$';
  878.         break;
  879.  
  880.     case ' ':            /* when not in a quote */
  881.     case '\t':            /* white space separates words */
  882.     case '\n':
  883.         if (state&DQUOTE) {
  884.         STR_NEXT(&str, *cmd);
  885.         } else if (!isspace(last_char)) {
  886.         /* end the previous arg vector */
  887.         STR_NEXT(&str, '\0');
  888.         ct++;            /* start a new one */
  889.         }
  890.         last_char = *cmd;
  891.         break;
  892.  
  893.     default:
  894.         STR_NEXT(&str, *cmd);
  895.         last_char = *cmd;
  896.     }
  897.     cmd++;                /* advance to next char */
  898.     }
  899.     if (state&DQUOTE) {
  900.     *error = "no match for opening \"";
  901.     return NULL;
  902.     }
  903.     if (state&GROUP) {
  904.     *error = "no match for $(";
  905.     return NULL;
  906.     }
  907.  
  908.     if (isspace(last_char)) {
  909.     --ct;                /* don't count just blanks */
  910.     }
  911.     STR_NEXT(&str, '\0');        /* null terminate the strings */
  912.     return build_argv(str.p, ct);
  913. }
  914.  
  915. /*
  916.  * build_argv - build arg vectors from inline strings
  917.  *
  918.  * build_cmd_line produces chars with null characters separating
  919.  * strings.  build_argv takes these chars and turns them into
  920.  * an arg vector suitable for execv.
  921.  *
  922.  * Caution: the value returned by build_argv() points to a region
  923.  *        which may be reused on subsequent calls to build_argv().
  924.  */
  925. static char **
  926. build_argv(p, ct)
  927.     register char *p;            /* strings, one after another */
  928.     register int ct;            /* count of strings */
  929. {
  930.     static char **argv = NULL;        /* reusable vector area */
  931.     static int argc;
  932.     register char **argp;
  933.  
  934.     if (argv == NULL) {
  935.     argc = ct + 1;
  936.     argv = (char **)xmalloc(argc * sizeof(*argv));
  937.     } else {
  938.     if (ct + 1 > argc) {
  939.         X_CHECK(argv);
  940.         argc = ct + 1;
  941.         argv = (char **)xrealloc((char *)argv, argc * sizeof(*argv));
  942.     }
  943.     }
  944.     argp = argv;
  945.     DEBUG(DBG_REMOTE_MID, "cmd =");
  946.     while (ct--) {
  947.     *argp++ = p;
  948.     DEBUG1(DBG_REMOTE_MID, " '%s'", p);
  949.     if (ct) {
  950.         while (*p++) ;        /* scan for next string */
  951.     }
  952.     }
  953.     DEBUG(DBG_REMOTE_MID, "\n");
  954.     *argp = NULL;            /* terminate vectors */
  955.     return argv;
  956. }
  957.  
  958. /*
  959.  * substitute - relace a $paramater with its value
  960.  *
  961.  * panic on errors, see build_cmd_line for details.
  962.  */
  963. static char *
  964. substitute(addr, file, found, var, len)
  965.     struct addr *addr;            /* source for $host, $addr, $user */
  966.     char *file;                /* source for $file */
  967.     char *found;            /* $value value */
  968.     register char *var;            /* start of variable */
  969.     register int len;            /* length of variable */
  970. {
  971.     static char buf[50];
  972.  
  973. #define MATCH(x) (len==sizeof(x)-1 && strncmpic(var, x, sizeof(x)-1) == 0)
  974.  
  975.     if (strncmpic(var, "lc:", sizeof("lc:") - 1) == 0) {
  976.     return lc_fold(substitute(addr, found, file, var + 3, len - 3));
  977.     }
  978.     if (strncmpic(var, "uc:", sizeof("uc:") - 1) == 0) {
  979.     return uc_fold(substitute(addr, found, file, var + 3, len - 3));
  980.     }
  981.     if (strncmpic(var, "strip:", sizeof("strip:") - 1) == 0) {
  982.     return strip_fold(substitute(addr, found, file, var + 6, len - 6));
  983.     }
  984.     if (strncmpic(var, "parent:", sizeof("parent:") - 1) == 0) {
  985.     struct addr *parent = addr->parent;
  986.  
  987.     if (parent == NULL) {
  988.         return NULL;
  989.     }
  990.     return substitute(parent, found, file, var + 7, len - 7);
  991.     }
  992.     if (strncmpic(var, "top:", sizeof("top:") - 1) == 0) {
  993.     struct addr *top = addr;
  994.  
  995.     while (top->parent) {
  996.         top = top->parent;
  997.     }
  998.     return substitute(top, found, file, var + 4, len - 4);
  999.     }
  1000.     if (MATCH("value")) {
  1001.     return found;
  1002.     }
  1003.     if (MATCH("grade")) {
  1004.     static char grade_str[2] = { 0, 0 };
  1005.     grade_str[0] = msg_grade;
  1006.     return grade_str;
  1007.     }
  1008.     if (MATCH("user") || MATCH("addr")) {
  1009.     return addr? addr->next_addr: NULL;
  1010.     }
  1011.     if (MATCH("input_addr")) {
  1012.     return addr? addr->in_addr: NULL;
  1013.     }
  1014.     if (MATCH("host")) {
  1015.     return addr? addr->next_host: NULL;
  1016.     }
  1017.     if (MATCH("HOME") || MATCH("home")) {
  1018.     return addr? addr->home: NULL;
  1019.     }
  1020.     if (MATCH("sender") || MATCH("from")) {
  1021.     return sender;
  1022.     }
  1023.     if (MATCH("sender_name") || MATCH("fullname")) {
  1024.     if (sender_name == NULL && islocal) {
  1025.         getfullname();
  1026.     }
  1027.     return sender_name;
  1028.     }
  1029.     if (MATCH("file")) {
  1030.     return file;
  1031.     }
  1032.     if (MATCH("message_id") || MATCH("id")) {
  1033.     return message_id;
  1034.     }
  1035.     if (MATCH("message_size") || MATCH("size")) {
  1036.     sprintf(buf, "%ld", msg_size);
  1037.     return buf;
  1038.     }
  1039.     if (MATCH("message_body_size") || MATCH("body_size")) {
  1040.     sprintf(buf, "%ld", msg_body_size);
  1041.     return buf;
  1042.     }
  1043.     if (MATCH("ctime")) {
  1044.     return unix_date();
  1045.     }
  1046.     if (MATCH("date")) {
  1047.     /* get the current date in ARPA format */
  1048.     return get_arpa_date(time((long *)0));
  1049.     }
  1050.     if (MATCH("spool_date")) {
  1051.     /* get the spool date in ARPA format */
  1052.     return get_arpa_date(message_date());
  1053.     }
  1054.     if (MATCH("$") || MATCH("pid")) {
  1055.     static char pidbuf[10];
  1056.  
  1057.     (void) sprintf(pidbuf, "%d", getpid());
  1058.     return pidbuf;
  1059.     }
  1060.     if (MATCH("uucp_name")) {
  1061.     return uucp_name;
  1062.     }
  1063.     if (MATCH("visible_name") || MATCH("name")) {
  1064.     return visible_name;
  1065.     }
  1066.     if (MATCH("primary_name") || MATCH("primary")) {
  1067.     return primary_name;
  1068.     }
  1069.     if (MATCH("version")) {
  1070.     return version_number;
  1071.     }
  1072.     if (MATCH("version_string")) {
  1073.     return version();
  1074.     }
  1075.     if (MATCH("release_date") || MATCH("release")) {
  1076.     return release_date;
  1077.     }
  1078.     if (MATCH("patch_number") || MATCH("patch")) {
  1079.     return patch_number;
  1080.     }
  1081.     if (MATCH("patch_date")) {
  1082.     return patch_date;
  1083.     }
  1084.     if (MATCH("bat")) {
  1085.     return bat;
  1086.     }
  1087.     if (MATCH("compile_num") || MATCH("ld_num")) {
  1088.     static char s_compile_num[10];
  1089.     (void) sprintf(s_compile_num, "%d", compile_num);
  1090.     return s_compile_num;
  1091.     }
  1092.     if (MATCH("compile_date") || MATCH("ld_date")) {
  1093.     return compile_date;
  1094.     }
  1095.     if (MATCH("smail_lib_dir") || MATCH("lib_dir")) {
  1096.     return smail_lib_dir;
  1097.     }
  1098.     if (MATCH("sender_host")) {
  1099.     return sender_host;
  1100.     }
  1101.     if (MATCH("sender_host_addr")) {
  1102.     return sender_host_addr;
  1103.     }
  1104.     if (MATCH("sender_proto")) {
  1105.     return sender_proto;
  1106.     }
  1107.     if (MATCH("program")) {
  1108.     return program;
  1109.     }
  1110.     if (MATCH("sender_program")) {
  1111.     return sender_program? sender_program: program;
  1112.     }
  1113.     if (MATCH("director")) {
  1114.     if (addr && addr->director)
  1115.         return addr->director->name;
  1116.     return NULL;
  1117.     }
  1118.     if (MATCH("router")) {
  1119.     if (addr && addr->router)
  1120.         return addr->router->name;
  1121.     return NULL;
  1122.     }
  1123.     if (MATCH("transport")) {
  1124.     if (addr && addr->transport)
  1125.         return addr->transport->name;
  1126.     return NULL;
  1127.     }
  1128.     return NULL;            /* no match */
  1129. #undef    MATCH
  1130. }
  1131.  
  1132. /*
  1133.  * lc_fold - meta substitution to convert value to lower case
  1134.  */
  1135. char *
  1136. lc_fold(value)
  1137.     register char *value;
  1138. {
  1139.     static int lc_size;            /* keep size of allocated region */
  1140.     int value_size;
  1141.     static char *lc = NULL;        /* retained malloc region */
  1142.     register char *p;            /* for scanning through lc */
  1143.  
  1144.     if (value == NULL) {
  1145.     return NULL;
  1146.     }
  1147.     value_size = strlen(value) + 1;
  1148.  
  1149.     /* get a region at least large enough for the value */
  1150.     if (lc == NULL) {
  1151.     lc = xmalloc(lc_size = value_size);
  1152.     } else if (value_size > lc_size) {
  1153.     X_CHECK(lc);
  1154.     lc = xrealloc(lc, lc_size = value_size);
  1155.     }
  1156.     p = lc;
  1157.     while (*value) {
  1158.     *p++ = lowercase(*value++);
  1159.     }
  1160.     *p = '\0';
  1161.     return lc;
  1162. }
  1163.  
  1164. /*
  1165.  * uc_fold - meta substitution to convert value to upper case
  1166.  */
  1167. char *
  1168. uc_fold(value)
  1169.     register char *value;
  1170. {
  1171.     static int uc_size;            /* keep size of allocated region */
  1172.     int value_size;
  1173.     static char *uc = NULL;        /* retained malloc region */
  1174.     register char *p;            /* for scanning through lc */
  1175.  
  1176.     if (value == NULL) {
  1177.     return NULL;
  1178.     }
  1179.     value_size = strlen(value) + 1;
  1180.  
  1181.     /* get a region at least large enough for the value */
  1182.     if (uc == NULL) {
  1183.     uc = xmalloc(uc_size = value_size);
  1184.     } else if (value_size > uc_size) {
  1185.     X_CHECK(uc);
  1186.     uc = xrealloc(uc, uc_size = value_size);
  1187.     }
  1188.     p = uc;
  1189.     while (*value) {
  1190.     *p++ = uppercase(*value++);
  1191.     }
  1192.     *p = '\0';
  1193.     return uc;
  1194. }
  1195.  
  1196. /*
  1197.  * strip_fold - strip quotes and collapse spaces and dots
  1198.  *
  1199.  * strip quotes from the input string and collapse any sequence of one
  1200.  * or more white space and `.' characters into a single `.'.
  1201.  */
  1202. static char *
  1203. strip_fold(value)
  1204.     char *value;
  1205. {
  1206.     static int strip_size;        /* keep size of allocated region */
  1207.     int value_size;
  1208.     static char *strip_buf = NULL;    /* retained malloc region */
  1209.     register char *p;            /* for scanning through strip_buf */
  1210.     register char *q;            /* also for scanning strip_buf */
  1211.  
  1212.     if (value == NULL) {
  1213.     return NULL;
  1214.     }
  1215.     value_size = strlen(value) + 1;
  1216.     if (strip_buf == NULL) {
  1217.     strip_buf = xmalloc(strip_size = value_size);
  1218.     } else if (value_size > strip_size) {
  1219.     X_CHECK(strip_buf);
  1220.     strip_buf = xrealloc(strip_buf, strip_size = value_size);
  1221.     }
  1222.     (void) strcpy(strip_buf, value);
  1223.     (void) strip(strip_buf);
  1224.  
  1225.     /* q reads and p writes */
  1226.     p = q = strip_buf;
  1227.     /* strip initial -'s */
  1228.     while (*q == '-') {
  1229.     q++;
  1230.     }
  1231.     while (*q) {
  1232.     /* collapse multiple white-space chars and .'s into single dots */
  1233.     if (isspace(*q) || *q == '.') {
  1234.         while (isspace(*++q) || *q == '.') ;
  1235.         *p++ = '.';
  1236.         continue;
  1237.     }
  1238.     *p++ = *q++;
  1239.     }
  1240.     *p = '\0';            /* finish off strip_buf */
  1241.     return strip_buf;
  1242. }
  1243.  
  1244. /*
  1245.  * skip_ident - skip a variable identifier
  1246.  */
  1247. static char *
  1248. skip_ident(s, lim)
  1249.     char *s, *lim;
  1250. {
  1251.     char *p = s;
  1252.     while (*p && (lim == NULL || p < lim)) {
  1253.     if (p != s && !isalnum(*p & 0xFF) && *p != '_')
  1254.         break;
  1255.     ++p;
  1256.     }
  1257.     return p;
  1258. }
  1259.  
  1260. /*
  1261.  * clause_token - extract a name or {...} token from the expansion string
  1262.  *
  1263.  * Used within ${...} substrings.
  1264.  */
  1265.  
  1266. static int
  1267. clause_token(sp, startp, lenp)
  1268.     char **sp;
  1269.     char **startp;
  1270.     int *lenp;
  1271. {
  1272.     char *s = *sp;
  1273.  
  1274.     while (isspace(*s))
  1275.     s++;
  1276.     switch (*s) {
  1277.     case '\0':
  1278.     case '}':
  1279.     return FAIL;
  1280.  
  1281.     case '{':
  1282.     s++;
  1283.     *startp = s;
  1284.     if (skip_nesting(&s) == FAIL)
  1285.         return FAIL;
  1286.     *lenp = s - *startp - 1;
  1287.     *sp = s;
  1288.     return SUCCEED;
  1289.  
  1290.     case ':':
  1291.     s++;
  1292.     while (isspace(*s))
  1293.         s++;
  1294.     /* FALL THROUGH */
  1295.  
  1296.     default:
  1297.     *startp = s;
  1298.     if (*s == ':' || *s == '{' || *s == '}')
  1299.         return FAIL;
  1300.     s++;
  1301.     while (*s) {
  1302.         switch (*s) {
  1303.         case '_':
  1304.         case '-':
  1305.         case '.':
  1306.         case '/':
  1307.         s++;
  1308.         continue;
  1309.  
  1310.         default:
  1311.         if (isalnum(*s)) {
  1312.             s++;
  1313.             continue;
  1314.         }
  1315.         break;
  1316.         }
  1317.         break;
  1318.     }
  1319.     *lenp = s - *startp;
  1320.     *sp = s;
  1321.     return SUCCEED;
  1322.  
  1323.     }
  1324. }
  1325.  
  1326. static int
  1327. skip_nesting(sp)
  1328.     char **sp;
  1329. {
  1330.     int nesting = 1;
  1331.     char *s = *sp;
  1332.  
  1333.     while (nesting) {
  1334.     switch (*s++) {
  1335.       case '\0':
  1336.         DEBUG1(DBG_DRIVER_LO, "expand_string: no matching } for {%s\n",
  1337.            *sp);
  1338.         return FAIL;
  1339.       case '{':
  1340.         ++nesting;
  1341.         break;
  1342.       case '}':
  1343.         --nesting;
  1344.         break;
  1345.     }
  1346.     }
  1347.     *sp = s;
  1348.     return SUCCEED;
  1349. }
  1350.  
  1351. #ifndef NODEBUG
  1352. /*
  1353.  * bad_subst - generate a debugging message for a failed substitution.
  1354.  *
  1355.  * note that we can't use "%*.*s" here since dprintf() is simple-minded.
  1356.  */
  1357. static void
  1358. bad_subst(var, len)
  1359.     char *var;
  1360.     int len;
  1361. {
  1362.     int c_save = var[len];
  1363.     var[len] = 0;
  1364.     DEBUG1(DBG_DRIVER_LO, "expand_string: expansion failed for ${%s\n", var);
  1365.     var[len] = c_save;
  1366. }
  1367. #endif
  1368.